'use strict';
var AOP = AOP || {};
(function ($) {
//Separated from it's addition to Foundation as it reuses itself during execution under certain conditions, and
//placing it separately like this makes the point more clearly.
var isbn13MultilineTextarea = function (el, required, parent) {
$(el).off('keypress').on('keypress', function (e) {
$(parent).removeClass('error');
});
$(el).val($(el).val().replace(/(^[ \t]*\n)/gm, ''));
$('span[data-isbn-error="true"]').remove();
if ($(el).val() === '') {
if (required && $(el).data('emptyError')) {
$(parent).find('.error').html($(el).data('emptyError'));
}
return !required;
}
//
// Allows 13 digit ISBNs, ignores whitespace either side of each line using the following.
// - Multiline mode /m
// - Global mode /g (whole input).
//
// Then for each line match any line entry that doesn't satisfy ALL of these conditions:
// - Zero or more leading whitespace characters: (\s+)?
// - Any 12 numbers: \d{12}
// - Any further number or an X character: (\d|X)
// - Zero or more trailing whitespace characters: (\s+)?
//
var re = /^((?!^(\s+)?\d{12}(\d|X)(\s+)?$).)*$/mg;
var val = el.value;
var m;
var valid = true;
var errors = [];
while ((m = re.exec(val)) !== null) {
if (m.index === re.lastIndex) {
re.lastIndex++;
}
// View your result using the m-variable.
// eg m[0] etc.
valid = false;
errors.push({
input: m[0],
index: m.index
});
}
if (!valid) {
var errorHtml = $('');
errors.forEach(function (v) {
if (errorHtml.html().length > 0) {
errorHtml.append($('
'));
}
var remove = $('(delete)');
remove.on('click', (function (el, instanceDetails) {
return function (ev) {
ev.preventDefault();
var index = instanceDetails.index;
var input = instanceDetails.input;
var length = input.length;
var originalValue = $(el).val();
var newValue = originalValue.substr(0, index) + originalValue.substr(index + length);
$(el).val(newValue.replace(/\n$/, ''));
//This isn't recursion, but a re-firing of the validator after a click.
isbn13MultilineTextarea(el, required, parent);
};
})(el, v));
$(errorHtml).append($('Invalid ISBN: "' + v.input + '" ').append(remove));
});
$(parent).find('.error').html(errorHtml);
}
if (valid) {
$(parent).removeClass('error');
}
//We're okay now, assuming it wasn't empty too!
return valid;
};
//NOTE ISSN not ISBN!
var issnMultilineTextarea = function (el, required, parent) {
$(el).off('keypress').on('keypress', function (e) {
$(parent).removeClass('error');
});
$(el).val($(el).val().replace(/(^[ \t]*\n)/gm, ''));
$('span[data-issn-error="true"]').remove();
if ($(el).val() === '') {
if (required && $(el).data('emptyError')) {
$(parent).find('.error').html($(el).data('emptyError'));
}
return !required;
}
//
// Allows 8 digit ISSNs, ignores whitespace either side of each line using the following.
// - Multiline mode /m
// - Global mode /g (whole input).
//
// Then for each line match any line entry that doesn't satisfy ALL of these conditions:
// - Zero or more leading whitespace characters: (\s+)?
// - Any 4 numbers, lowercase or uppercase letters: (\d|[a-z]|[A-Z]){4}
// - An optional space or dash (ISSNs often contain a dash or space in the middle): (-|\s)?
// - Any 4 numbers, lowercase or uppercase letters: (\d|[a-z]|[A-Z]){4}
// - Zero or more trailing whitespace characters: (\s+)?
//
var re = /^((?!^(\s+)?(\d|[a-z]|[A-Z]){4}(-|\s)?(\d|[a-z]|[A-Z]){4}(\s+)?$).)*$/mg;
var val = el.value;
var m;
var valid = true;
var errors = [];
while ((m = re.exec(val)) !== null) {
if (m.index === re.lastIndex) {
re.lastIndex++;
}
// View your result using the m-variable.
// eg m[0] etc.
valid = false;
errors.push({
input: m[0],
index: m.index
});
}
if (!valid) {
var errorHtml = $('');
errors.forEach(function (v) {
if (errorHtml.html().length > 0) {
errorHtml.append($('
'));
}
var remove = $('(delete)');
remove.on('click', (function (el, instanceDetails) {
return function (ev) {
ev.preventDefault();
var index = instanceDetails.index;
var input = instanceDetails.input;
var length = input.length;
var originalValue = $(el).val();
var newValue = originalValue.substr(0, index) + originalValue.substr(index + length);
$(el).val(newValue.replace(/\n$/, ''));
//This isn't recursion, but a re-firing of the validator after a click.
issnMultilineTextarea(el, required, parent);
};
})(el, v));
$(errorHtml).append($('Invalid ISSN: "' + v.input + '" ').append(remove));
});
$(parent).find('.error').html(errorHtml);
}
if (valid) {
$(parent).removeClass('error');
}
//We're okay now, assuming it wasn't empty too!
return valid;
};
var orcidRegex = /^((?:(?:\d{4}[-]){3}\d{3}(\d|X)))$/;
// This regex catches a few more invalid emails - better than standard foundation version
// Eg, it does not allow emails like 'test...@test.com'
// http://davidcel.is/posts/stop-validating-email-addresses-with-regex/
var emailRegex = /^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})$/;
AOP.emailRegex = emailRegex; // CORE-2678 various locations expect this to be public.
$(document).foundation({
abide: {
timeout: 0,
live_validate: false,
validate_on_blur: true,
patterns: {
slug: /^[0-9-a-zA-Z]*$/,
corepassword: /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/,
openUrlCustomText: /^.{0,60}$/,
// Override Foundation email regex
email: emailRegex
},
validators: {
orcidval: function (el, required, parent) {
var primaryErrorEl = $(parent).find('#primary-error');
var customErrorEl = $(parent).find('#custom-error');
//setup messages
primaryErrorEl.hide();
customErrorEl.hide();
var value = el.value;
var output = false;
//test for empty and return success
if (value === '') {
primaryErrorEl.show();
return true;
}
//test for format and return failure
if (!value.match(orcidRegex)) {
primaryErrorEl.show();
return false;
}
$.ajax({
url: '//pub.orcid.org/v1.1/search/orcid-bio/?q=orcid:' + value,
headers: {
'Accept': 'application/json'
},
async: false // foundation won't validate with async request here
}).success(function (data) {
var results = data['orcid-search-results']['num-found'];
if (results === 0) {
customErrorEl.show();
output = false;
} else {
output = true;
}
}).error(function () {
//TODO: confirm with PO about this - what should we do if cannot validate orcid
output = true;
});
return output;
},
// This version does not perform the AJAX request, it just checks the format
orcidformat: function (el, required, parent) {
var primaryErrorEl = $(parent).find('#primary-error');
var customErrorEl = $(parent).find('#custom-error');
//setup messages
primaryErrorEl.hide();
customErrorEl.hide();
var value = el.value;
var output = false;
//test for empty and return success
if (value === '') {
primaryErrorEl.show();
return true;
}
//test for format and return failure
if (!value.match(orcidRegex)) {
primaryErrorEl.show();
return false;
} else {
primaryErrorEl.hide();
return true;
}
},
unique: function (el, required, parent) {
var primaryErrorEl = $(parent).find('#primary-error');
var customErrorEl = $(parent).find('#custom-error');
var selected = $(parent).find('#selectedValues').val().split(',').filter(Boolean);
var val = $(el).val().toString().toLowerCase().trim();
if (val && selected.indexOf(val) >= 0) {
primaryErrorEl.hide();
customErrorEl.show();
return false;
} else {
primaryErrorEl.show();
customErrorEl.hide();
return true;
}
},
/*
* Have combined a uniqueness check with subscription number validation
* Issues in Foundation 5.5.2 - If you combine a custom validator with
* a pattern or type validation, the custom validator takes precedence
* and returns a valid result even if the pattern/type fails.
*/
subscriptionNumber: function (el, required, parent) {
var regex = /^\d{10}$/;
var primaryErrorEl = $(parent).find('#primary-error');
var customErrorEl = $(parent).find('#custom-error');
var selected = $(parent).find('#selectedValues').val().split(',').filter(Boolean);
var value = $(el).val().trim();
if (value && selected.indexOf(value) >= 0) {
primaryErrorEl.hide();
customErrorEl.show();
return false;
} else if (value && !value.match(regex)) {
primaryErrorEl.show();
customErrorEl.hide();
return false;
} else {
primaryErrorEl.hide();
customErrorEl.hide();
return true;
}
},
/*
* Have combined a uniqueness check with email format validation
* Issues in Foundation 5.5.2 - If you combine a custom validator with
* a pattern or type validation, the custom validator takes precedence
* and returns a valid result even if the pattern/type fails.
*/
shibboleth: function (el, required, parent) {
var primaryErrorEl = $(parent).find('#primary-error');
var customErrorEl = $(parent).find('#custom-error');
var selected = $(parent).find('#selectedValues').val().split(',').filter(Boolean);
var value = $(el).val().toString().toLowerCase().trim();
if (value && selected.indexOf(value) >= 0) {
primaryErrorEl.hide();
customErrorEl.show();
return false;
} else if ((value && !value.match(emailRegex)) || !value) {
primaryErrorEl.show();
customErrorEl.hide();
return false;
} else {
primaryErrorEl.hide();
customErrorEl.hide();
return true;
}
},
optionRequired: function (el, required, parent) {
var selected = $(parent).find('input:checked').val();
var primaryErrorEl = $(parent).find('#primary-error');
if (!selected) {
primaryErrorEl.show();
return false;
} else {
$(parent).find('label').removeClass('error');
primaryErrorEl.hide();
return true;
}
},
// Improved email validation check
email: function (el, required, parent) {
return el.value.match(emailRegex);
},
checkbox_limit: function (el, required, parent) {
var group = parent.closest('.checkbox-group, .radio-group');
var min = group.attr('data-abide-validator-min');
var checked = group.find(':checked').length;
var errors = group.find('div.error, label.error');
if (checked >= min) {
group.removeClass('error');
group.find('small.error').hide();
errors.removeClass('error');
return true;
} else {
group.addClass('error');
group.find('small.error').css({
display: 'block'
});
return false;
}
},
aopEnhanced: function (el) {
return $(el).is('[data-aop-is-valid]');
},
/**
* This is a reasonably complex pair of validators, as it goes above and beyond to offer the user the ability to
* repair their field inline using controls in the error message.
*
* @param el
* @param required
* @param parent
* @returns {boolean}
*/
isbn13MultilineTextarea: isbn13MultilineTextarea,
issnMultilineTextarea: issnMultilineTextarea,
isSanctionedCountry: function (el, required, parent) {
var endpoint = AOP.baseUrl + '/services/aop-cambridge-membership/konakart/sanctioned';
var isSanctioned = $(el).find('option:selected').attr('data-sanctioned');
var primaryError = $(el).siblings('.country-required-error');
var customError = $(el).siblings('.sanctioned-country-error');
primaryError.hide();
customError.hide();
if (!el.value || el.value.length === 0) {
customError.hide();
primaryError.show();
return false;
}
if (isSanctioned) {
customError.show();
primaryError.hide();
return false;
}
$(parent).removeClass('error');
$(parent).siblings('div').find('label').removeClass('error');
return true;
},
/**
* This will validate if a given field has a valid length
* We have to ensure this because kk will reject the payload
* if a field is invalid
* field lengths can be found here \kona-kart-client\lib\address\max-field-lengths.js
*
* @param el
* @param required
* @param parent
* @returns {boolean}
*/
isValidKonaKartAddress: function (el, required, parent) {
var elLength = el.value.length;
var elMaxLength = $(el).data('fieldMax');
var elFieldName = $(el).data('fieldName');
var primaryError = $(el).siblings('.required-error');
var customError = $(el).siblings('.custom-error');
var customError2 = $(el).siblings('.validity-error');
if (required && (!el.value || el.value.length === 0)) {
primaryError.show();
customError.hide();
return false;
} else if (elLength > elMaxLength) {
primaryError.hide();
customError.show();
return false;
} else if (elFieldName === 'postcode') {
var countryName = $('#kkCountryCode option:selected').html().trim();
var pattern;
var postCode = el.value;
if (countryName === 'United States') {
pattern = AOP.konakart.validation.usPostCodePattern;
} else if (countryName === 'Canada') {
pattern = AOP.konakart.validation.caPostCodePattern;
} else {
pattern = AOP.konakart.validation.defaultPostCodePattern;
}
if (!pattern.test(postCode)) {
primaryError.hide();
customError.hide();
customError2.show();
return false;
}
}
primaryError.hide();
customError.hide();
customError2.hide();
return true;
}
}
}
});
})(jQuery);